I stumbled across a surprising (to me) fact.
console.log("asdf".replace(/.*/g, "x"));
Why two replacements? It seems any non-empty string without newlines will produce exactly two replacements for this pattern. Using a replacement function, I can see that the first replacement is for the entire string, and the second is for an empty string.
As per the ECMA-262 standard, String.prototype.replace calls RegExp.prototype[@@replace], which says:
11. Repeat, while done is false
a. Let result be ? RegExpExec(rx, S).
b. If result is null, set done to true.
c. Else result is not null,
i. Append result to the end of results.
ii. If global is false, set done to true.
iii. Else,
1. Let matchStr be ? ToString(? Get(result, "0")).
2. If matchStr is the empty String, then
a. Let thisIndex be ? ToLength(? Get(rx, "lastIndex")).
b. Let nextIndex be AdvanceStringIndex(S, thisIndex, fullUnicode).
c. Perform ? Set(rx, "lastIndex", nextIndex, true).
where rx is /.*/g and S is 'asdf'.
See 11.c.iii.2.b:
b. Let nextIndex be AdvanceStringIndex(S, thisIndex, fullUnicode).
Therefore in 'asdf'.replace(/.*/g, 'x') it is actually:
[], lastIndex = 0'asdf', results = [ 'asdf' ], lastIndex = 4'', results = [ 'asdf', '' ], lastIndex = 4, AdvanceStringIndex, set lastIndex to 5null, results = [ 'asdf', '' ], returnTherefore there are 2 matches.
The first match is obviously "asdf" (Position [0,4]). Because the global flag (g) is set, it continues searching. At this point (Position 4), it finds a second match, an empty string (Position [4,4]).
Remember that * matches zero or more elements.